<template>
  <uploader :options="options" :file-status-text="statusText" class="uploader-example" ref="uploaderRef"
    @file-complete="fileComplete" @complete="complete" @file-added="onFileAdded" @file-error="onFileError">
    <!--     Your browser, unfortunately, is not supported by Uploader.js. The library requires support for -->
    <uploader-unsupport></uploader-unsupport>

    <uploader-drop>
      <p style="color:#909399">Drop files here to upload or</p>
      <uploader-btn>select files</uploader-btn>

      <!-- <uploader-btn :attrs="attrs">select images</uploader-btn> -->
      <!-- <uploader-btn :directory="true">select folder</uploader-btn> -->
    </uploader-drop>

    <!--    <uploader-list></uploader-list>-->

    <uploader-list v-show="filePanelShow">
      <template #default="{ fileList }">
        <div class="file-panel" :class="{ collapse: collapse }">
          <div class="file-title">
            <div class="title">文件列表</div>
            <div class="operate">
              <el-button :title="collapse ? '展开' : '折叠'" link @click="collapse = !collapse">
                <el-icon>
                  <FullScreen v-if="collapse" />
                  <Minus v-else />
                </el-icon>
              </el-button>
              <el-button title="关闭" link @click="closeFilePanel">
                <el-icon>
                  <Close />
                </el-icon>
              </el-button>
            </div>
          </div>

          <ul class="file-list">
            <li v-for="file in fileList.slice().reverse()" :key="file.id" class="file-item">
              <uploader-file ref="files" :class="['file_' + file.id, customStatus]" :file="file"
                :list="true"></uploader-file>
            </li>
            <div v-if="!fileList.length" class="no-file">
              暂无待上传文件
            </div>
          </ul>
        </div>
      </template>
    </uploader-list>
  </uploader>
</template>

<script>
import SparkMD5 from 'spark-md5'
import request from '@/request'
import { useUserStore } from '@/store/user'

export default {
  data() {
    return {
      options: {
        target: 'api/file/upload',
        headers: function(file, chunk) {
          const token = localStorage.getItem('token')
          return token ? { Authorization: `Bearer ${token}` } : {}
        },
        // 是否开启服务器分片校验
        testChunks: true,
        simultaneousUploads: 3, // 并发数1 ， 如果并发数大于1，那么后端需要将上传的块逐个新建文件，最后再统一合并，否则文件可能会损坏。
        // 服务器分片校验函数，秒传及断点续传基础。这个函数，每个块都会执行一次。
        checkChunkUploadedByResponse: function (chunk, message) {
          let skip = false
          try {
            console.log('服务器返回的数据' , message)
            let objMessage = JSON.parse(message)
            if (objMessage.data.skipUpload) {
              skip = true
            } else {
              // 返回的已经上传的块，做比较。已经上传的就跳过。
              skip = (objMessage.data.uploaded || []).indexOf(chunk.offset + 1) >= 0
            }
          } catch (e) {
            console.log(e)
          }
          console.log('是否跳过' , skip)
          return skip
        },
        // 用于转换文件上传状态文本映射对象
        fileStatusText: function (status, response) {
          // 第一个 status 为状态，第二个为响应内容
          const statusTextMap = {
            success: '成功',
            error: '失败',
            uploading: '上传中',
            paused: '暂停',
            waiting: '等待'
          }
          return statusTextMap[status]
        }
      },
      statusText: {
        success: 'success',
        error: 'error',
        uploading: 'uploading',
        paused: 'paused',
        waiting: 'waiting'
      },
      uploaderRef: null,
      md5Percentage: 0,
      customStatus: {},
      collapse: false,
      filePanelShow: false
    }
  },
  created() {
    // console.log(" start created"+  this.$refs.uploaderRef.uploader)
  },
  methods: {
    complete() {
      console.log(' complete', arguments)
    },
    fileComplete() {
      console.log('file complete', arguments)
    },
    // 并发刷新保护
    refreshingPromise: null,
    async onFileError(...args) {
      // 兼容不同签名：尝试从参数推断出 file 与 chunk
      let rootFile = null
      let file = null
      let message = null
      let chunk = null

      if (args.length === 4) {
        ;[rootFile, file, message, chunk] = args
      } else if (args.length === 3) {
        ;[file, message, chunk] = args
      } else if (args.length >= 2) {
        file = args[0]
        message = args[1]
        chunk = args[2]
      }

      try {
        const status = chunk?.xhr?.status || chunk?.status || chunk?.xhrStatus
        if (status !== 401 && status !== 403) return

        // 并发情况下只刷新一次，其余等待
        if (!this.refreshingPromise) {
          this.refreshingPromise = this.$refreshUploadToken()
        }
        await this.refreshingPromise
        this.refreshingPromise = null

        // 刷新成功后，重试该分片/文件
        if (chunk && typeof chunk.retry === 'function') {
          chunk.retry()
        } else if (file && typeof file.retry === 'function') {
          file.retry()
        } else if (file && typeof file.resume === 'function') {
          file.resume()
        } else if (this.$refs.uploaderRef?.uploader) {
          this.$refs.uploaderRef.uploader.upload()
        }
      } catch (e) {
        console.error('刷新令牌并重试失败: ', e)
      }
    },
    async $refreshUploadToken() {
      const userStore = useUserStore()
      const currentRefreshToken = userStore?.refreshToken || localStorage.getItem('refreshToken')
      if (!currentRefreshToken) {
        try { userStore?.logout?.() } catch (_) {}
        const redirect = encodeURIComponent(location.hash.replace('#', '') || '/')
        location.hash = `#/login?redirect=${redirect}`
        throw new Error('缺少refreshToken')
      }

      const resp = await request({ url: '/auth/refresh', method: 'post', data: { refreshToken: currentRefreshToken } })
      const payload = resp?.data !== undefined ? resp.data : resp
      const tokens = (payload && payload.data) ? payload.data : payload
      const newAccessToken = tokens?.token || tokens?.accessToken || ''
      const newRefreshToken = tokens?.refreshToken || ''

      if (!newAccessToken) throw new Error('刷新令牌返回空token')

      try {
        userStore.updateTokens({ token: newAccessToken, refreshToken: newRefreshToken })
      } catch (_) {
        localStorage.setItem('token', newAccessToken)
        if (newRefreshToken) localStorage.setItem('refreshToken', newRefreshToken)
      }

      // headers 回调已从 localStorage 读取，无需手动 setOption
      return true
    },
    onFileAdded(file) {
      this.filePanelShow = true
      console.log('file onFileAdded', arguments)
      const uploaderInstance = this.$refs.uploaderRef.uploader
      console.log('file onFileAdded uploaderInstance', uploaderInstance)
      // 将额外的参数赋值到每个文件上，以不同文件使用不同params的需求
      file.params = this.params
      // 计算MD5
      this.computeMD5(file).then((result) => this.startUpload(result))
    },
    /**
     * 新增的自定义的状态: 'md5'、'merging'、'transcoding'、'failed'
     * @param id
     * @param status
     */
    statusSet(id, status) {
      const statusMap = {
        md5: {
          text: '校验MD5',
          bgc: '#fff'
        },
        merging: {
          text: '合并中',
          bgc: '#e2eeff'
        },
        transcoding: {
          text: '转码中',
          bgc: '#e2eeff'
        },
        failed: {
          text: '上传失败',
          bgc: '#e2eeff'
        }
      }
      this.customStatus.value = status

      let that = this
      that.$nextTick(function () {
        const statusTag = document.createElement('div')
        statusTag.className = `custom-status-${id} custom-status`
        statusTag.innerText = statusMap[status].text
        statusTag.style.backgroundColor = statusMap[status].bgc

        const statusWrap = document.querySelector(`.file_${id} .uploader-file-status`)
        console.log('修改样式' , statusTag)
        statusWrap.appendChild(statusTag)
      })

    },
    statusRemove(id) {
      this.customStatus.value = ''
      let that = this
      that.$nextTick(() => {
        const statusTag = document.querySelector(`.custom-status-${id}`)
        statusTag.remove()
      })
    },
    /**
     * 计算md5值，以实现断点续传及秒传
     * @param file
     * @returns Promise
     */
    computeMD5(file) {
      // 文件状态设为"计算MD5"
      this.statusSet(file.id, 'md5')
      let fileReader = new FileReader()
      let time = new Date().getTime()
      let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
      let currentChunk = 0
      const chunkSize = 10 * 1024 * 1000
      let chunks = Math.ceil(file.size / chunkSize)
      let spark = new SparkMD5.ArrayBuffer()

      file.pause()
      // 文件状态设为"计算MD5"。目前不支持自定义状态
      loadNext()

      let that = this

      return new Promise((resolve, reject) => {
        fileReader.onload = (e) => {
          spark.append(e.target.result)

          if (currentChunk < chunks) {
            currentChunk++

            if (!file.paused) {
              console.log('文件取消了')
            }


            loadNext()

            // 实时展示MD5的计算进度
            this.$nextTick(() => {
              let md5ProgressFloat = ((currentChunk / chunks) * 100).toFixed(0)
              const md5ProgressText = '校验MD5 ' + md5ProgressFloat + '%'
              document.querySelector(`.custom-status-${file.id}`).innerText = md5ProgressText
              console.log(md5ProgressText)
              that.md5Percentage = parseFloat(md5ProgressFloat)
            })

          } else {
            let md5 = spark.end()

            // md5计算完毕
            resolve({ md5, file })

            that.md5Percentage = 0
            this.statusRemove(file.id)
            console.log(`MD5计算完毕：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${file.size} 用时：${new Date().getTime() - time} ms`
            )

          }
        }

        fileReader.onerror = function () {
          this.error(`文件${file.name}读取出错，请检查该文件`)
          file.cancel()
          reject()
        }
      })

      function loadNext() {
        let start = currentChunk * chunkSize
        let end = start + chunkSize >= file.size ? file.size : start + chunkSize

        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
      }
    },

    // md5计算完毕，开始上传
    startUpload({ md5, file }) {
      file.uniqueIdentifier = md5
      file.resume()
      console.log('file pause end and start upload',md5,file)
    },
    closeFilePanel() {
      this.filePanelShow = false
    }
  },
  mounted() {
    console.log('start mount1', this.$refs.uploaderRef.uploader)
    // this.uploaderRef.value.uploader =  window.uploader
    this.$nextTick(() => {
      window.uploader = this.$refs.uploaderRef.uploader
    })
  },
  computed: {}

}
</script>

<style lang="scss">
.uploader-example {
  padding: 15px;
  margin: 40px auto 0;
  font-size: 12px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.4);
}

.uploader-example .uploader-btn {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  line-height: 1;
  height: 22px;
  margin-right: 4px;
  border-radius: 5px;
  background: #409eff;
  color: #fff;
  border: 0px solid #666;
  padding: 8px 15px;
  text-align: center;
}

.uploader-example .uploader-list {
  max-height: 440px;
  overflow: auto;
  overflow-x: hidden;
  overflow-y: auto;
}

@media screen and (max-width: 600px) {

  /*当屏幕尺寸小于600px时，应用下面的CSS样式*/
  .file-panel {
    width: 90%;
    margin: 0 auto;
  }
}

@media screen and (min-width: 600px) {

  /*当屏幕尺寸小于600px时，应用下面的CSS样式*/
  .file-panel {
    width: 520px;
    right: 15px;
  }
}

.file-panel {

  position: fixed;
  z-index: 20;

  bottom: 15px;
  box-sizing: border-box;

  background-color: #fff;
  border: 1px solid #e2e2e2;
  border-radius: 7px 7px 0 0;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);

  .file-title {
    display: flex;
    height: 40px;
    line-height: 40px;
    padding: 0 15px;
    border-bottom: 1px solid #ddd;

    .operate {
      flex: 1;
      text-align: right;

      .el-button {
        --el-button-hover-link-text-color: #67C23A;

        + .el-button {
          margin-left: 8px;
        }
      }
    }
  }

  .file-list {
    position: relative;
    height: 240px;
    overflow-x: hidden;
    overflow-y: auto;
    background-color: #fff;
    transition: all 0.3s;
    padding: 0;
    margin: 0;

    .file-item {
      background-color: #fff;
    }
  }

  &.collapse {
    .file-title {
      background-color: #e7ecf2;
    }

    .file-list {
      height: 0;
    }
  }

  .uploader-file {
    &.md5 {
      .uploader-file-resume {
        display: none;
      }
    }
  }

  .custom-status {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
  }
}
</style>
